import { Axios, AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";

export interface IResponseData<T = any> {
  code: 200 | 400 | 401 | 403 | 404 | 405 | 410 | 500 | 502 | 503 | 504;
  data: T;
}

export interface IErrorData {
  error: string;
  message: string;
}

export type ResponseErrorOrUndefined = IErrorData | undefined;

export type ResponseOKString = IResponseData<string>;

export class ApiHeadBuilder {
  private cache = 0;
  private forceUpdate = false;
  private token = "";
  protected headers: Record<string, string> = {};

  public SetCache(cache: number) {
    this.cache = cache;
    return this;
  }

  public SetForceUpdate(forceUpdate: boolean) {
    this.forceUpdate = forceUpdate;
    return this;
  }

  public SetToken(token: string) {
    this.token = token;
    return this;
  }

  public build(): Record<string, string> {
    this.headers = {};

    this.buildToken();
    this.buildCache();
    this.buildForceUpdate();

    return this.headers;
  }

  private buildToken() {
    if (this.token.length > 0) {
      this.headers["Authorization"] = "Bearer " + this.token;
    }
  }

  private buildCache() {
    if (this.cache > 0) {
      this.headers["cache"] = this.cache.toString();
    }
  }

  private buildForceUpdate() {
    if (this.forceUpdate) {
      this.headers["forceUpdate"] = this.forceUpdate.toString();
    }
  }
}

export interface IApiConfig<T = any> {
  params: T; // Get Delete 使用
  data: T; // Post Put 使用
  cache: number;
  forceUpdate: boolean;
  token: string;
}

export class ApiConfig implements IApiConfig {
  params = undefined;
  data = undefined;
  cache = 0;
  forceUpdate = false;
  token = "";
}

export class ApiUtil {
  public axios: Axios;
  public subPath: string; // 记录 API 的子路径

  /**
   *
   * @param axios axios 对象
   * @param subPath 此 api 的通用路径
   */
  constructor(axios: Axios, subPath = "") {
    this.axios = axios;
    this.subPath = subPath;
  }

  /**
   * 生成发送用的 URL
   * @param url 用于拼接的 URL 参数
   * @private
   */
  private genUrl(url: string) {
    if (this.subPath.length === 0 || url.startsWith("http")) {
      return url;
    } else {
      if (!url.startsWith("/")) {
        url = "/" + url;
      }
      return this.subPath + url;
    }
  }

  private static onAxiosReady<T extends IResponseData>(
    resolve: (value: PromiseLike<T> | T) => void,
    reject: (reason?: any) => void
  ) {
    return (res: AxiosResponse) => {
      console.log("[ApiUtil]:", res.config.url, res);

      const data: T = res.data;
      if (res.status <= 299 || data.code <= 299) {
        resolve(data);
      } else {
        reject(data);
      }
    };
  }

  // 捕获所有的异常情况 提示服务器异常!
  private static onCatch<T extends IResponseData>(
    reason: AxiosError,
    resolve: (value: PromiseLike<T> | T) => void,
    reject: (reason?: any) => void
  ) {
    if (reason.response) {
      ApiUtil.onAxiosReady<T>(resolve, reject)(reason.response);
    } else {
      const _error: IErrorData = {
        error: "ServerError",
        message: "服务器未正确响应",
      };
      reject({
        code: 500,
        data: _error,
      });
    }
  }

  public get<T extends IResponseData>(
    url: string,
    config?: AxiosRequestConfig
  ): Promise<T> {
    const _config: AxiosRequestConfig = { ...config };
    return new Promise<T>((resolve, reject) => {
      this.axios
        .get(this.genUrl(url), _config)
        .then(ApiUtil.onAxiosReady<T>(resolve, reject))
        .catch((reason) => {
          ApiUtil.onCatch<T>(reason, resolve, reject);
        });
    });
  }

  public post<T extends IResponseData>(
    url: string,
    data: any,
    config?: AxiosRequestConfig
  ): Promise<T> {
    const _config: AxiosRequestConfig = { ...config };
    return new Promise<T>((resolve, reject) => {
      this.axios
        .post(this.genUrl(url), data, _config)
        .then(ApiUtil.onAxiosReady<T>(resolve, reject))
        .catch((reason) => {
          ApiUtil.onCatch<T>(reason, resolve, reject);
        });
    });
  }

  public put<T extends IResponseData>(
    url: string,
    data: any,
    config?: AxiosRequestConfig
  ): Promise<T> {
    const _config: AxiosRequestConfig = { ...config };
    return new Promise<T>((resolve, reject) => {
      this.axios
        .put(this.genUrl(url), data, _config)
        .then(ApiUtil.onAxiosReady<T>(resolve, reject))
        .catch((reason) => {
          ApiUtil.onCatch<T>(reason, resolve, reject);
        });
    });
  }

  public delete<T extends IResponseData>(
    url: string,
    config?: AxiosRequestConfig
  ): Promise<T> {
    const _config: AxiosRequestConfig = { ...config };
    return new Promise<T>((resolve, reject) => {
      this.axios
        .delete(this.genUrl(url), _config)
        .then(ApiUtil.onAxiosReady<T>(resolve, reject))
        .catch((reason) => {
          ApiUtil.onCatch<T>(reason, resolve, reject);
        });
    });
  }

  public getWithApiConfig<T extends IResponseData>(
    url: string,
    cfg: Partial<IApiConfig>
  ): Promise<T> {
    return this.get<T>(url, this.genAPIConfig(cfg));
  }

  public postWithApiConfig<T extends IResponseData>(
    url: string,
    cfg: Partial<IApiConfig>
  ): Promise<T> {
    return this.post<T>(url, cfg.data, this.genAPIConfig(cfg));
  }

  public putWithApiConfig<T extends IResponseData>(
    url: string,
    cfg: Partial<IApiConfig>
  ): Promise<T> {
    return this.put<T>(url, cfg.data, this.genAPIConfig(cfg));
  }

  public deleteWithApiConfig<T extends IResponseData>(
    url: string,
    cfg: Partial<IApiConfig>
  ): Promise<T> {
    return this.delete<T>(url, this.genAPIConfig(cfg));
  }

  private genAPIConfig(user: Partial<IApiConfig>): AxiosRequestConfig {
    const config = {
      ...new ApiConfig(),
      ...user,
    };

    return {
      params: config.params,
      data: config.data,
      headers: new ApiHeadBuilder()
        .SetToken(config.token)
        .SetCache(config.cache)
        .SetForceUpdate(config.forceUpdate)
        .build(),
    };
  }
}
